// Unique ID creation requires a high quality random # generator. In the browser we therefore // require the crypto API and do not support built-in fallback to lower quality random number // generators (like Math.random()). var getRandomValues; var rnds8 = new Uint8Array(16); function rng() { // lazy load so that environments that need to polyfill have a chance to do so if (!getRandomValues) { // getRandomValues needs to be invoked in a context where "this" is a Crypto implementation. Also, // find the complete implementation of crypto (msCrypto) on IE11. getRandomValues = typeof crypto !== 'undefined' && crypto.getRandomValues && crypto.getRandomValues.bind(crypto) || typeof msCrypto !== 'undefined' && typeof msCrypto.getRandomValues === 'function' && msCrypto.getRandomValues.bind(msCrypto); if (!getRandomValues) { throw new Error('crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported'); } } return getRandomValues(rnds8); } var REGEX = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i; function validate(uuid) { return typeof uuid === 'string' && REGEX.test(uuid); } /** * Convert array of 16 byte values to UUID string format of the form: * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX */ var byteToHex = []; for (var i = 0; i < 256; ++i) { byteToHex.push((i + 0x100).toString(16).substr(1)); } function stringify(arr) { var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; // Note: Be careful editing this code! It's been tuned for performance // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434 var uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase(); // Consistency check for valid UUID. If this throws, it's likely due to one // of the following: // - One or more input array values don't map to a hex octet (leading to // "undefined" in the uuid) // - Invalid input values for the RFC `version` or `variant` fields if (!validate(uuid)) { throw TypeError('Stringified UUID is invalid'); } return uuid; } function v4(options, buf, offset) { options = options || {}; var rnds = options.random || (options.rng || rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` rnds[6] = rnds[6] & 0x0f | 0x40; rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided if (buf) { offset = offset || 0; for (var i = 0; i < 16; ++i) { buf[offset + i] = rnds[i]; } return buf; } return stringify(rnds); } /*! js-cookie v3.0.1 | MIT */ /* eslint-disable no-var */ function assign (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { target[key] = source[key]; } } return target } /* eslint-enable no-var */ /* eslint-disable no-var */ var defaultConverter = { read: function (value) { if (value[0] === '"') { value = value.slice(1, -1); } return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent) }, write: function (value) { return encodeURIComponent(value).replace( /%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g, decodeURIComponent ) } }; /* eslint-enable no-var */ /* eslint-disable no-var */ function init (converter, defaultAttributes) { function set (key, value, attributes) { if (typeof document === 'undefined') { return } attributes = assign({}, defaultAttributes, attributes); if (typeof attributes.expires === 'number') { attributes.expires = new Date(Date.now() + attributes.expires * 864e5); } if (attributes.expires) { attributes.expires = attributes.expires.toUTCString(); } key = encodeURIComponent(key) .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent) .replace(/[()]/g, escape); var stringifiedAttributes = ''; for (var attributeName in attributes) { if (!attributes[attributeName]) { continue } stringifiedAttributes += '; ' + attributeName; if (attributes[attributeName] === true) { continue } // Considers RFC 6265 section 5.2: // ... // 3. If the remaining unparsed-attributes contains a %x3B (";") // character: // Consume the characters of the unparsed-attributes up to, // not including, the first %x3B (";") character. // ... stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]; } return (document.cookie = key + '=' + converter.write(value, key) + stringifiedAttributes) } function get (key) { if (typeof document === 'undefined' || (arguments.length && !key)) { return } // To prevent the for loop in the first place assign an empty array // in case there are no cookies at all. var cookies = document.cookie ? document.cookie.split('; ') : []; var jar = {}; for (var i = 0; i < cookies.length; i++) { var parts = cookies[i].split('='); var value = parts.slice(1).join('='); try { var foundKey = decodeURIComponent(parts[0]); jar[foundKey] = converter.read(value, foundKey); if (key === foundKey) { break } } catch (e) {} } return key ? jar[key] : jar } return Object.create( { set: set, get: get, remove: function (key, attributes) { set( key, '', assign({}, attributes, { expires: -1 }) ); }, withAttributes: function (attributes) { return init(this.converter, assign({}, this.attributes, attributes)) }, withConverter: function (converter) { return init(assign({}, this.converter, converter), this.attributes) } }, { attributes: { value: Object.freeze(defaultAttributes) }, converter: { value: Object.freeze(converter) } } ) } var api = init(defaultConverter, { path: '/' }); /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __values(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); } function __read(o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; } var convertToNative = function (data, options) { var e_1, _a; try { for (var _b = __values(Object.entries(data)), _c = _b.next(); !_c.done; _c = _b.next()) { var _d = __read(_c.value, 2), key = _d[0], value = _d[1]; if (value !== undefined) { switch (key) { case "NULL": return null; case "BOOL": return Boolean(value); case "N": return convertNumber(value, options); case "B": return convertBinary(value); case "S": return convertString(value); case "L": return convertList(value, options); case "M": return convertMap(value, options); case "NS": return new Set(value.map(function (item) { return convertNumber(item, options); })); case "BS": return new Set(value.map(convertBinary)); case "SS": return new Set(value.map(convertString)); default: throw new Error("Unsupported type passed: " + key); } } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_1) throw e_1.error; } } throw new Error("No value defined: " + JSON.stringify(data)); }; var convertNumber = function (numString, options) { if (options === null || options === void 0 ? void 0 : options.wrapNumbers) { return { value: numString }; } var num = Number(numString); var infinityValues = [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY]; if ((num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) && !infinityValues.includes(num)) { if (typeof BigInt === "function") { try { return BigInt(numString); } catch (error) { throw new Error(numString + " can't be converted to BigInt. Set options.wrapNumbers to get string value."); } } else { throw new Error(numString + " is outside SAFE_INTEGER bounds. Set options.wrapNumbers to get string value."); } } return num; }; var convertString = function (stringValue) { return stringValue; }; var convertBinary = function (binaryValue) { return binaryValue; }; var convertList = function (list, options) { return list.map(function (item) { return convertToNative(item, options); }); }; var convertMap = function (map, options) { return Object.entries(map).reduce(function (acc, _a) { var _b; var _c = __read(_a, 2), key = _c[0], value = _c[1]; return (__assign(__assign({}, acc), (_b = {}, _b[key] = convertToNative(value, options), _b))); }, {}); }; var unmarshall = function (data, options) { return convertToNative({ M: data }, options); }; const qs = Object.fromEntries(new URLSearchParams(window.location.search).entries()); const config = JSON.parse(`{"endpoint":"https://cue77nm612.execute-api.ca-central-1.amazonaws.com/journey","cookie":{"name":"fsdeid","expires":365,"secure":true,"domain":".fourseasons.com"},"functions":{"recordView":{"enabled":true},"recordSearch":{"enabled":true},"getState":{"enabled":true},"recordReservation":{"enabled":true}}}`); //disable functions based on query strings const functionsToDisable = qs.fsDeFu_DisableAll ? Object.keys(config.functions) : (qs.fsDeFu_Disable || '').split(','); functionsToDisable.forEach( func => { if(config.functions[func]){ config.functions[func].enabled = false; } }); const logError = (func, e) => console.error(`Caught error in decisioning engine: ${func}`, e); const recordView = async ({property, section}) => { const uuidVal = await verifyPermissionAndUuid(); try{ await fetch(`${config.endpoint}/${uuidVal}/view`, { method: "PUT", body: JSON.stringify({ property: capitalize(property), section: section && section.toLowerCase ? section.toLowerCase() : undefined, timestamp: getTimestamp(), url: window.location.href, }), headers: { 'content-type': 'application/json' } }); }catch(e){ logError('recordView', e); } }; const recordSearch = async ({property, checkin, checkout, promo, rooms}) => { const uuidVal = await verifyPermissionAndUuid(); try { await fetch(`${config.endpoint}/${uuidVal}/search`, { method: "PUT", body: JSON.stringify({ property: capitalize(property), checkin, checkout, promo, rooms: rooms.map( ({bed_code, offer_code, adults, children}) => ({bed_code, offer_code, adults, children})), timestamp: getTimestamp() }), headers: { 'content-type': 'application/json' } }); }catch(e){ logError('recordSearch', e); } }; const recordReservation = async ({property}) => { const uuidVal = await verifyPermissionAndUuid(); try { await fetch(`${config.endpoint}/${uuidVal}/reservation`, { method: "PUT", body: JSON.stringify({ property: capitalize(property), timestamp: getTimestamp() }), headers: { 'content-type': 'application/json' } }); }catch(e){ logError('recordReservation', e); } }; const getState = async (propertyCode) => { const uuidVal = await verifyPermissionAndUuid(); try{ const state = await fetch(`${config.endpoint}/${uuidVal}`).then( res => res.json()); //update the cookie value createCookie(uuidVal); if( state.Item ){ const fullState = state.Item.data ? unmarshall(state.Item).data : {}; return propertyCode ? fullState[propertyCode] || {} : fullState }else { return {}; } }catch(e){ logError('getState', e); return {}; } }; const capitalize = text => text && text.toUpperCase ? text.toUpperCase() : undefined; const createCookie = uuidVal => { api.set(config.cookie.name, uuidVal, { domain: config.cookie.domain, expires: config.cookie.expires, secure: config.cookie.secure }); }; //function to verify cookie acceptance and setup uuid, if needed const verifyPermissionAndUuid = async () => { return new Promise(resolve => { window.FS.GDPR.ifAllowed('functional', () => { //this will only be called if that permission has been granted //get or create a uuid let uuidVal = api.get(config.cookie.name); if(!uuidVal){ uuidVal = v4(); createCookie(uuidVal); } resolve(uuidVal); }); }); }; const getTimestamp = () => (new Date()).toISOString(); // if consent is withdrawn, delete the cookie window.FS.GDPR.onConsentChange(consent => { if(!consent.functional){ api.remove(config.cookie.name); } }); window.FS = window.FS || {}; window.FS.DECISION_ENGINE = { recordView: config.functions.recordView.enabled ? recordView : () => {}, recordSearch: config.functions.recordSearch.enabled ? recordSearch : () => {}, getState: config.functions.getState.enabled ? getState : () => ({}), recordReservation: config.functions.recordReservation.enabled ? recordReservation : () => { }, getCookie: () => api.get(config.cookie.name) };